/*
 Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
 Copyright (C) 2014 Sony Computer Entertainment Inc.

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Library General Public
 License as published by the Free Software Foundation; either
 version 2 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Library General Public License for more details.

 You should have received a copy of the GNU Library General Public License
 along with this library; see the file COPYING.LIB.  If not, write to
 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 Boston, MA 02110-1301, USA.
 */

#include "config.h"

#if USE(ACCELERATED_COMPOSITING) && USE(ACAGI)
#include "AcagiTiledBackingStore.h"

#include "AcagiCompositor.h"
#include "ImageBuffer.h"

namespace WebCore {

class GraphicsLayer;

AcagiTiledBackingStore::AcagiTiledBackingStore()
    : m_contentsScale(1)
{
}

void AcagiTiledBackingStore::updateContentsFromImageIfNeeded(AcagiCompositor* compositor)
{
    if (!m_image)
        return;

    updateContents(compositor, m_image.get(), m_image->size(), m_image->rect(), AcagiBitmapTexture::UpdateCannotModifyOriginalImageData);
    m_image.clear();
}

TransformationMatrix AcagiTiledBackingStore::adjustedTransformForRect(const FloatRect& targetRect)
{
    return TransformationMatrix::rectToRect(rect(), targetRect);
}

void AcagiTiledBackingStore::compositeContents(AcagiCompositor* compositor, const FloatRect& targetRect, const TransformationMatrix& transform, float opacity)
{
    updateContentsFromImageIfNeeded(compositor);
    TransformationMatrix adjustedTransform = transform * adjustedTransformForRect(targetRect);
    for (size_t i = 0; i < m_tiles.size(); ++i)
        m_tiles[i].paint(compositor, adjustedTransform, opacity, calculateExposedTileEdges(rect(), m_tiles[i].tileRect()));
}

void AcagiTiledBackingStore::drawBorder(AcagiCompositor* compositor, const Color& borderColor, float borderWidth, const FloatRect& targetRect, const TransformationMatrix& transform)
{
    TransformationMatrix adjustedTransform = transform * adjustedTransformForRect(targetRect);
    for (size_t i = 0; i < m_tiles.size(); ++i)
        compositor->drawBorder(borderColor, borderWidth, m_tiles[i].tileRect(), adjustedTransform);
}

void AcagiTiledBackingStore::drawRepaintCounter(AcagiCompositor* compositor, int repaintCount, const Color& borderColor, const FloatRect& targetRect, const TransformationMatrix& transform)
{
    TransformationMatrix adjustedTransform = transform * adjustedTransformForRect(targetRect);
    for (size_t i = 0; i < m_tiles.size(); ++i)
        compositor->drawNumber(repaintCount, borderColor, m_tiles[i].tileRect().location(), adjustedTransform);
}

void AcagiTiledBackingStore::createOrDestroyTilesIfNeeded(const FloatSize& size, const FloatRect& actualVisibleRect, float contentsScale, const IntSize& tileSize, bool hasAlpha)
{
    // Calculate visible rect in the device coordrinates.
    FloatRect scaledVisibleRect = actualVisibleRect;
    scaledVisibleRect.intersect(FloatRect(FloatPoint::zero(), size));
    scaledVisibleRect.scale(contentsScale);

    IntSize scaledSize = expandedIntSize(FloatSize(size.width() * contentsScale, size.height() * contentsScale));

    // CoverRect represents a visible area in the tile coordinates.
    FloatRect rect = scaledVisibleRect;
    rect.scale(1.0 / tileSize.width(), 1.0 / tileSize.height());
    IntRect coveredRect = enclosingIntRect(rect); 

//    printf("COV: (%.1f, %.1f, %.1f, %.1f)\n", coveredRect.x(), coveredRect.y(), coveredRect.width(), coveredRect.height());

    if (size == m_size && m_contentsScale == contentsScale && coveredRect == m_coveredRect)
        return;

    m_size = size;
    m_coveredRect = coveredRect;

    // Remove all tiles if contents scale has been changed.
    if (m_contentsScale != contentsScale)
        m_tiles.clear();
    m_contentsScale = contentsScale;

    Vector<IntRect> tileRectsToAdd;
    Vector<int> tileIndicesToRemove;
    static const size_t TileEraseThreshold = 0;

    // This method recycles tiles. We check which tiles we need to add, which to remove, and use as many
    // removable tiles as replacement for new tiles when possible.
    for (int y = 0; y < coveredRect.height(); y++) {
        for (int x = 0; x < coveredRect.width(); x++) {
            IntRect tileRect((x + coveredRect.x()) *  tileSize.width(), (y + coveredRect.y()) * tileSize.height(), tileSize.width(), tileSize.height());
            tileRect.intersect(IntRect(IntPoint::zero(), scaledSize));
            if (!tileRect.isEmpty())
                tileRectsToAdd.append(tileRect);
        }
    }

    // Check which tiles need to be removed, and which already exist.
    for (int i = m_tiles.size() - 1; i >= 0; --i) {
        IntRect oldTileRect = m_tiles[i].tileRect();
        bool existsAlready = false;

        for (int j = tileRectsToAdd.size() - 1; j >= 0; --j) {
            IntRect newTileRect = tileRectsToAdd[j];
            if (oldTileRect != newTileRect)
                continue;

            // A tile that we want to add already exists, no need to add or remove it.
            existsAlready = true;
            tileRectsToAdd.remove(j);
            break;
        }

        // This tile is not needed.
        if (!existsAlready)
            tileIndicesToRemove.append(i);
    }

    // Recycle removable tiles to be used for newly requested tiles.
    for (size_t i = 0; i < tileRectsToAdd.size(); ++i) {
        if (!tileIndicesToRemove.isEmpty()) {
            // We recycle an existing tile for usage with a new tile rect.
            AcagiTile& tile = m_tiles[tileIndicesToRemove.last()];
            tileIndicesToRemove.removeLast();
            tile.setTileRect(tileRectsToAdd[i], m_contentsScale);

            if (tile.texture())
                tile.texture()->reset(tileRectsToAdd[i].size(), hasAlpha);
            continue;
        }

        m_tiles.append(AcagiTile(tileRectsToAdd[i], m_contentsScale));
    }

    // Remove unnecessary tiles, if they weren't recycled.
    // We use a threshold to make sure we don't create/destroy tiles too eagerly.
    for (size_t i = 0; i < tileIndicesToRemove.size() && m_tiles.size() > TileEraseThreshold; ++i)
        m_tiles.remove(tileIndicesToRemove[i]);
}

void AcagiTiledBackingStore::updateContents(AcagiCompositor* compositor, Image* image, const FloatSize& totalSize, const IntRect& dirtyRect, AcagiBitmapTexture::UpdateContentsFlag updateContentsFlag)
{
    float ScaleForImage = 1;
    createOrDestroyTilesIfNeeded(totalSize, FloatRect(FloatPoint::zero(), totalSize), ScaleForImage, compositor->maxTextureSize(), !image->currentFrameKnownToBeOpaque());
    for (size_t i = 0; i < m_tiles.size(); ++i)
        m_tiles[i].updateContents(compositor, image, dirtyRect, updateContentsFlag);
}

void AcagiTiledBackingStore::updateContents(AcagiCompositor* compositor, GraphicsLayer* sourceLayer, const FloatSize& totalSize, const FloatRect& visibleContentsRect, const IntRect& dirtyRect, AcagiBitmapTexture::UpdateContentsFlag updateContentsFlag)
{
    createOrDestroyTilesIfNeeded(totalSize, visibleContentsRect, compositor->contentsScale(), compositor->maxTextureSize(), true);
    for (size_t i = 0; i < m_tiles.size(); ++i)
        m_tiles[i].updateContents(compositor, sourceLayer, dirtyRect, updateContentsFlag);
}

PassRefPtr<AcagiBitmapTexture> AcagiTiledBackingStore::texture() const
{
    for (size_t i = 0; i < m_tiles.size(); ++i) {
        RefPtr<AcagiBitmapTexture> texture = m_tiles[i].texture();
        if (texture)
            return texture;
    }

    return PassRefPtr<AcagiBitmapTexture>();
}

} // namespace WebCore
#endif
